/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.user.client.ui;

import cc.alcina.framework.gwt.client.util.ClientUtils;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.builder.shared.HtmlBuilderFactory;
import com.google.gwt.dom.builder.shared.HtmlElementBuilder;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;

/**
 * EXPERIMENTAL and subject to change. Do not use this in production code.
 * <p>
 * A simple {@link Element} implementation (<strong>not</strong> an actual dom
 * object) that can serve as stand in to be used by {@link IsRenderable} widgets
 * before they are fully built. For example, it can accumulate simple set*()
 * values to be used when the widget is actually ready to render. Thus, most
 * {@link IsRenderable} widget code can be written without taking into account
 * whether or not the widget has yet been rendered.
 * <p>
 * {@link DOM#appendChild} is aware of PotentialElement, and calls its resolve()
 * method. This triggers a call to {@link UIObject#resolvePotentialElement()},
 * which widgets can customize to get a real {@link Element} in place at the
 * last moment.
 * 
 * TODO(rdcastro): Cover all unsupported methods with helpful error messages.
 */
public class PotentialElement extends Element {
	static {
		declareShim();
	}

	public static PotentialElement as(Element e) {
		assert isPotential(e);
		return (PotentialElement) e;
	}

	/**
	 * Builds a new PotentialElement with the tag name set to "div".
	 * 
	 * @see #build(UIObject,String)
	 */
	public static PotentialElement build(UIObject o) {
		return build(o, "div");
	}

	/**
	 * Builds a new PotentialElement. This element keeps track of the
	 * {@link UIObject} so that it can call
	 * {@link UIObject#resolvePotentialElement} to get a real element when that
	 * is needed.
	 */
	public static native PotentialElement build(UIObject o, String tagName) /*-{
		var el = new $wnd.GwtPotentialElementShim();
		el.tagName = tagName;
		el.__gwt_resolve = @com.google.gwt.user.client.ui.PotentialElement::buildResolveCallback(Lcom/google/gwt/user/client/ui/UIObject;)(o);
		return @com.google.gwt.dom.client.Element::as(Lcom/google/gwt/core/client/JavaScriptObject;)(el);
	}-*/;

	/**
	 * Creates an {@link HtmlElementBuilder} instance inheriting all attributes
	 * set for the given PotentialElement.
	 * 
	 * @param potentialElement
	 *            assumed to be a PotentialElement, used as basis for the
	 *            builder
	 * @return a propertly configured {@link HtmlElementBuilder} instance
	 */
	public static HtmlElementBuilder createBuilderFor(Element potentialElement) {
		PotentialElement el = PotentialElement.as(potentialElement);
		HtmlElementBuilder builder = HtmlBuilderFactory.get().trustedCreate(
				el.getTagName());
		el.mergeInto(builder);
		return builder;
	}

	/**
	 * Tests whether a given {@link JavaScriptObject} represents a
	 * PotentialElement.
	 * 
	 * @param o
	 *            the {@link JavaScriptObject} to be tested
	 * @return true if the given object is a PotentialElement instance
	 */
	public static native boolean isPotential(JavaScriptObject o) /*-{
		try {
			return (!!o) && (!!o.__gwt_resolve);
		} catch (e) {
			return false;
		}
	}-*/;

	/**
	 * If given a PotentialElement, returns the real Element to be built from
	 * it. Otherwise returns the given Element itself.
	 * <p>
	 * Note that a PotentialElement can only be resolved once. Making repeated
	 * calls to this method with the same PotentialElement is an error.
	 */
	public static Element resolve(Element maybePotential) {
		return maybePotential.<PotentialElement> cast().resolve();
	}

	private static native JavaScriptObject buildResolveCallback(
			UIObject resolver) /*-{
		return function() {
			this.__gwt_resolve = @com.google.gwt.user.client.ui.PotentialElement::cannotResolveTwice();
			return resolver.@com.google.gwt.user.client.ui.UIObject::resolvePotentialElement()();
		};
	}-*/;

	private static final native void cannotResolveTwice() /*-{
		throw "A PotentialElement cannot be resolved twice.";
	}-*/;

	private static final native void declareShim() /*-{
		var shim = function() {
		};
		shim.prototype = {
			className : '',
			clientHeight : 0,
			clientWidth : 0,
			dir : '',
			getAttribute : function(name, value) {
				return this[name];
			},
			href : '',
			id : '',
			innerHTML : '',
			lang : '',
			// should be @com.google.gwt.dom.client.Node.ELEMENT_MODE, but the compiler
			// doesn't like that.
			nodeType : 1,
			removeAttribute : function(name, value) {
				this[name] = undefined;
			},
			setAttribute : function(name, value) {
				this[name] = value;
			},
			src : '',
			style : {},
			title : ''
		};
		$wnd.GwtPotentialElementShim = shim;
	}-*/;

	protected PotentialElement() {
	}

	final native Element setResolver(UIObject resolver) /*-{
		this.__gwt_resolve = @com.google.gwt.user.client.ui.PotentialElement::buildResolveCallback(Lcom/google/gwt/user/client/ui/UIObject;)(resolver);
	}-*/;

	/**
	 * Copy only the fields that have actually changed from the values in the
	 * shim prototype. Do this by severing the __proto__ link, allowing us to
	 * iterate only on the fields set in this specific instance.
	 */
	private native void mergeInto(HtmlElementBuilder builder) /*-{
    var savedProto = this.__proto__;
    var tagName = this.tagName;
    var gwtResolve = this.__gwt_resolve;
    var className = this.className;
    var innerHTML = this.innerHTML;
    this.innerHTML=null;

    try {
      this.__proto__ = null;
      this.tagName = null;
      this.__gwt_resolve = null;

      // className needs special treatment because the actual HTML attribute is
      // called "class" and not "className".
      if (this.className) {
        builder.@com.google.gwt.dom.builder.shared.ElementBuilder::className(Ljava/lang/String;)(
            this.className);
        this.className = null;
      }

      // Iterate over all attributes, and copy them to the ElementBuilder.
      // TODO(rdcastro): Deal with the "style" attribute.
      for (attr in this) {
        if (!this[attr]) {
          continue;
        }
        if (typeof this[attr] == 'number') {
          builder.@com.google.gwt.dom.builder.shared.ElementBuilder::attribute(Ljava/lang/String;I)(
              attr, this[attr]);
        } else if (typeof this[attr] == 'string') {
          builder.@com.google.gwt.dom.builder.shared.ElementBuilder::attribute(Ljava/lang/String;Ljava/lang/String;)(
              attr, this[attr]);
        }
      }
    } finally {
      this.__proto__ = savedProto;
      if (className) {
        this.className = className;
      }
      this.__gwt_resolve = gwtResolve;
      this.tagName = tagName;
    }
  }-*/;

	/**
	 * Calls the <code>__gwt_resolve</code> method on the underlying JavaScript
	 * object if it exists. On objects created via {@link #build}, this method
	 * is a call to the {@link UIObject#resolvePotentialElement} method on the
	 * associated UIObject.
	 */
	private native Element resolve() /*-{
		return this.__gwt_resolve ? this.__gwt_resolve() : this;
	}-*/;

	public final String getInnerText0() {
		return ClientUtils.simpleInnerText(getInnerHTML());
	}
}
